use std::sync::Arc;

use deno_core::{
    op2,
    v8::{self, ObjectTemplate, Value},
    OpState,
};
use ron::error::SpannedResult;
use QcCore::resources::{
    audio_clip::AudioClip,
    material::{self, Material},
    ResourceManager,
};
use QcRender::resources::{Mesh, MeshFile};
use QcTools::utils::r#ref::Ref;

use crate::utils::GoExt;

#[op2]
pub fn opLoadRes<'a>(
    state: &mut OpState,
    scope: &mut v8::HandleScope<'a>,
    #[string] name: &str,
) -> v8::Local<'a, v8::Value> {
    let resource_manager = state.borrow_mut::<*mut ResourceManager>();
    let resource_manager = unsafe { &mut **resource_manager };

    let res = resource_manager.get(name);

    if let Some(res) = res {
        let obj_temp = ObjectTemplate::new(scope);
        obj_temp.set_internal_field_count(1);
        let obj = obj_temp.new_instance(scope).unwrap();
        obj.set_internal_field(0, v8::External::new(scope, &res as *const _ as _).into());

        obj.into()
    } else {
        v8::undefined(scope).into()
    }
}

#[op2]
pub fn opLoadMaterial<'a>(
    state: &mut OpState,
    scope: &mut v8::HandleScope<'a>,
    #[string] name: &str,
) -> v8::Local<'a, v8::Value> {
    let resource_manager = state.borrow_mut::<Arc<ResourceManager>>();

    let res = resource_manager.get(name);

    if let Some(ref res) = res {
        let text = String::from_utf8(res.file.data.to_vec()).unwrap();

        let material: SpannedResult<Material> = ron::from_str(&text);
        if let Ok(material) = material {
            let material = Material::load(&material, resource_manager.clone());
            let index = resource_manager.add_material(material);
            let obj = v8::Object::new(scope);
            let key = v8::String::new(scope, "uuid").unwrap();
            let value = v8::Number::new(scope, index.to_bits() as _);

            obj.set(scope, key.into(), value.into());

            GoExt::set_prototype(scope, obj, "Material");
            return obj.into();
        }
    }

    return v8::undefined(scope).into();
}

#[op2]
pub fn opLoadJson<'a>(
    state: &mut OpState,
    scope: &mut v8::HandleScope<'a>,
    #[string] name: &str,
) -> v8::Local<'a, v8::Value> {
    let resource_manager = state.borrow_mut::<Arc<ResourceManager>>();

    let res = resource_manager.get(name);

    if let Some(res) = res {
        let text = String::from_utf8(res.file.data.to_vec()).unwrap();

        let text = v8::String::new(scope, &text).unwrap();

        text.into()
    } else {
        v8::undefined(scope).into()
    }
}

#[op2]
pub fn opLoadMesh<'a>(
    state: &mut OpState,
    scope: &mut v8::HandleScope<'a>,
    #[string] name: &str,
) -> v8::Local<'a, v8::Value> {
    let resource_manager = state.borrow_mut::<Arc<ResourceManager>>();

    let res = resource_manager.get(name);

    if let Some(ref res) = res {
        let text = String::from_utf8(res.file.data.to_vec()).unwrap();

        let mesh: SpannedResult<MeshFile> = ron::from_str(&text);
        if let Ok(mesh) = mesh {
            let mesh = Mesh::create(mesh.vertices, mesh.indices);
            let index = resource_manager.add_mesh(mesh);
            let obj = v8::Object::new(scope);
            let key = v8::String::new(scope, "uuid").unwrap();
            let value = v8::Number::new(scope, index.to_bits() as _);

            obj.set(scope, key.into(), value.into());

            GoExt::set_prototype(scope, obj, "Mesh");
            return obj.into();
        }
    }

    return v8::undefined(scope).into();
}

#[op2]
pub fn opLoadAudio<'a>(
    state: &mut OpState,
    scope: &mut v8::HandleScope<'a>,
    #[string] name: &str,
) -> v8::Local<'a, v8::Value> {
    let resource_manager = state.borrow_mut::<Arc<ResourceManager>>();

    let res = resource_manager.get(name);

    if let Some(ref res) = res {
        let data = res.file.data.to_vec();

        let audio_clip = AudioClip::new(data);

        let index = resource_manager.add_audio(audio_clip);
        let obj = v8::Object::new(scope);
        let key = v8::String::new(scope, "uuid").unwrap();
        let value = v8::Number::new(scope, index.to_bits() as _);

        obj.set(scope, key.into(), value.into());

        GoExt::set_prototype(scope, obj, "AudioClip");
        return obj.into();
    }

    return v8::undefined(scope).into();
}
